(function() {
    var Lexer;

    Lexer = (function() {
        var BOOLEAN, DBLSTRING, DIRECTIVES, FIELD_DEF_NOT_NULL, LITERAL, MATH, MATH_MULTI, NUMBER, PARAM_PLACEHOLDER, SEPARATOR, SQL_CONDITIONALS, SQL_FUNCTIONS, SQL_KEYWORDS, SQL_OPERATORS, SQL_SORT_ORDERS, STAR, STRING, STRING_OP, WHITESPACE;

        function Lexer(sql, opts) {
            var bytesConsumed, i;
            if (opts == null) {
                opts = {};
            }
            this.sql = sql;
            this.preserveWhitespace = opts.preserveWhitespace || false;
            this.tokens = [];
            this.params = [];
            this.currentLine = 1;
            i = 0;
            while (this.chunk = sql.slice(i)) {
                bytesConsumed = this.stringToken() || this.whitespaceToken() || this.parensToken() || this.seperatorToken() || this.directivesToken() || this.keywordToken() || this.numberToken() || this.starToken() || this.booleanToken() || this.functionToken() || this.sortOrderToken() || this.operatorToken() || this.mathToken() || this.stringOpToken() || this.dotToken() || this.conditionalToken() || this.bracketsToken() || this.literalToken() || this.paramToken();
                if (bytesConsumed < 1) {
                    break;
                }
                i += bytesConsumed;
            }
            this.token('EOF', '');
        }

        Lexer.prototype.token = function(name, value) {
            var result;
            result = this.tokens.push([name, value, this.currentLine]);
            if (name === "PARAM_PLACEHOLDER") {
                this.params.push([this.tokens.length - 1, name]);
            }
            return result;
        };

        Lexer.prototype.tokenizeFromRegex = function(name, regex, part, lengthPart, output) {
            var lstIndex, match, partMatch, quote;
            if (part == null) {
                part = 0;
            }
            if (lengthPart == null) {
                lengthPart = part;
            }
            if (output == null) {
                output = true;
            }
            if (!(match = regex.exec(this.chunk))) {
                return 0;
            }
            partMatch = match[part];
            if (output) {
                lstIndex = this.tokens.length - 1;
            }
            if (this.tokens[lstIndex] && this.tokens[lstIndex][0] === name && (name === 'DBLSTRING' || name === 'STRING')) {
                quote = (function() {
                    switch (name) {
                        case 'DBLSTRING':
                            return '"';
                        default:
                            return "'";
                    }
                })();
                this.tokens[lstIndex][1] = this.tokens[lstIndex][1] + quote + partMatch;
            }
            else {
                if (name === 'RIGHT_BRACKET' && this.tokens[lstIndex][0] !== 'LITERAL') {
                    this.tokens[lstIndex][0] = 'LITERAL';
                }
                this.token(name, partMatch);
            }
            return match[lengthPart].length;
        };

        Lexer.prototype.tokenizeFromWord = function(name, word) {
            var match, matcher;
            if (word == null) {
                word = name;
            }
            word = this.regexEscape(word);
            matcher = /^\w+$/.test(word) ? new RegExp("^(" + word + ")\\b", 'ig') : new RegExp("^(" + word + ")", 'ig');
            match = matcher.exec(this.chunk);
            if (!match) {
                return 0;
            }
            this.token(name, match[1]);
            return match[1].length;
        };

        Lexer.prototype.tokenizeFromList = function(name, list) {
            var entry, ret, _i, _len;
            ret = 0;
            for (_i = 0, _len = list.length; _i < _len; _i++) {
                entry = list[_i];
                ret = this.tokenizeFromWord(name, entry);
                if (ret > 0) {
                    break;
                }
            }
            return ret;
        };

        Lexer.prototype.keywordToken = function() {
            return this.tokenizeFromWord('INSERT') || this.tokenizeFromWord('INTO') || this.tokenizeFromWord('VALUES') || this.tokenizeFromWord('SELECT') || this.tokenizeFromWord('DISTINCT') || this.tokenizeFromWord('FROM') || this.tokenizeFromWord('WHERE') || this.tokenizeFromWord('GROUP') || this.tokenizeFromWord('ORDER') || this.tokenizeFromWord('BY') || this.tokenizeFromWord('HAVING') || this.tokenizeFromWord('LIMIT') || this.tokenizeFromWord('JOIN') || this.tokenizeFromWord('LEFT') || this.tokenizeFromWord('RIGHT') || this.tokenizeFromWord('INNER') || this.tokenizeFromWord('OUTER') || this.tokenizeFromWord('ON') || this.tokenizeFromWord('AS') || this.tokenizeFromWord('UNION') || this.tokenizeFromWord('ALL') || this.tokenizeFromWord('DELETE') || this.tokenizeFromWord('UPDATE') || this.tokenizeFromWord('SET') || this.tokenizeFromWord('CREATE') || this.tokenizeFromWord('TABLE') || this.tokenizeFromWord('PRIMARY') || this.tokenizeFromWord('KEY') || this.tokenizeFromWord('AUTOINCREMENT') || this.tokenizeFromWord('DROP') || this.tokenizeFromWord('ALTER') || this.tokenizeFromWord('RENAME') || this.tokenizeFromWord('TO') || this.tokenizeFromWord('NOT') || this.tokenizeFromWord('IN') || this.tokenizeFromWord('COLLATE') || this.tokenizeFromWord('NOCASE') || this.tokenizeFromWord('EXPLAIN') || this.tokenizeFromWord('QUERY') || this.tokenizeFromWord('PLAN') || this.tokenizeFromWord('CASE') || this.tokenizeFromWord('WHEN') || this.tokenizeFromWord('THEN') || this.tokenizeFromWord('ELSE') || this.tokenizeFromWord('END');
        };

        Lexer.prototype.dotToken = function() {
            return this.tokenizeFromWord('DOT', '.');
        };

        Lexer.prototype.operatorToken = function() {
            return this.tokenizeFromList('OPERATOR', SQL_OPERATORS);
        };

        Lexer.prototype.mathToken = function() {
            return this.tokenizeFromList('MATH', MATH) || this.tokenizeFromList('MATH_MULTI', MATH_MULTI);
        };

        Lexer.prototype.stringOpToken = function() {
            return this.tokenizeFromList('STRING_OP', STRING_OP);
        };

        Lexer.prototype.paramToken = function() {
            return this.tokenizeFromList('PARAM_PLACEHOLDER', PARAM_PLACEHOLDER);
        };

        Lexer.prototype.conditionalToken = function() {
            return this.tokenizeFromList('CONDITIONAL', SQL_CONDITIONALS);
        };

        Lexer.prototype.functionToken = function() {
            return this.tokenizeFromList('FUNCTION', SQL_FUNCTIONS);
        };

        Lexer.prototype.sortOrderToken = function() {
            return this.tokenizeFromList('DIRECTION', SQL_SORT_ORDERS);
        };

        Lexer.prototype.booleanToken = function() {
            return this.tokenizeFromList('BOOLEAN', BOOLEAN);
        };

        Lexer.prototype.directivesToken = function() {
            return this.tokenizeFromWord('FIELD_DEF_NOT_NULL', FIELD_DEF_NOT_NULL) || this.tokenizeFromList('DIRECTIVES', DIRECTIVES);
        };

        Lexer.prototype.starToken = function() {
            return this.tokenizeFromRegex('STAR', STAR);
        };

        Lexer.prototype.seperatorToken = function() {
            return this.tokenizeFromRegex('SEPARATOR', SEPARATOR);
        };

        Lexer.prototype.literalToken = function() {
            return this.tokenizeFromRegex('LITERAL', LITERAL, 1, 0);
        };

        Lexer.prototype.numberToken = function() {
            return this.tokenizeFromRegex('NUMBER', NUMBER);
        };

        Lexer.prototype.stringToken = function() {
            return this.tokenizeFromRegex('STRING', STRING, 1, 0) || this.tokenizeFromRegex('DBLSTRING', DBLSTRING, 1, 0);
        };

        Lexer.prototype.bracketsToken = function() {
            return this.tokenizeFromRegex('LEFT_BRACKET', /^\[/) || this.tokenizeFromRegex('RIGHT_BRACKET', /^\]/);
        };

        Lexer.prototype.parensToken = function() {
            return this.tokenizeFromRegex('LEFT_PAREN', /^\(/) || this.tokenizeFromRegex('RIGHT_PAREN', /^\)/);
        };

        Lexer.prototype.windowExtension = function() {
            var match;
            match = /^\.(win):(length|time)/i.exec(this.chunk);
            if (!match) {
                return 0;
            }
            this.token('WINDOW', match[1]);
            this.token('WINDOW_FUNCTION', match[2]);
            return match[0].length;
        };

        Lexer.prototype.whitespaceToken = function() {
            var match, newlines, partMatch;
            if (!(match = WHITESPACE.exec(this.chunk))) {
                return 0;
            }
            partMatch = match[0];
            newlines = partMatch.replace(/[^\n]/, '').length;
            this.currentLine += newlines;
            if (this.preserveWhitespace) {
                this.token(name, partMatch);
            }
            return partMatch.length;
        };

        Lexer.prototype.regexEscape = function(str) {
            return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
        };

        SQL_KEYWORDS = ['SELECT', 'FROM', 'WHERE', 'GROUP BY', 'ORDER BY', 'HAVING', 'AS', 'INSERT', 'INTO', 'UPDATE', 'SET', 'DELETE', 'CREATE TABLE', 'PRIMARY KEY', 'AUTOINCREMENT', 'DROP TABLE', 'IN', 'COLLATE NOCASE', 'EXPLAIN QUERY PLAN'];

        SQL_FUNCTIONS = ['AVG', 'COUNT', 'MIN', 'MAX', 'SUM', 'CAST'];

        SQL_SORT_ORDERS = ['ASC', 'DESC'];

        SQL_OPERATORS = ['<=', '>=', '<>', '==', '!=', '=', '>', '<', 'LIKE', 'IS NOT', 'IS'];

        SQL_CONDITIONALS = ['AND', 'OR'];

        BOOLEAN = ['TRUE', 'FALSE', 'NULL'];

        STRING_OP = ['||'];

        MATH = ['+', '-'];

        MATH_MULTI = ['/', '*'];

        FIELD_DEF_NOT_NULL = 'NOT NULL';

        DIRECTIVES = ['NOT NULL', 'IF NOT EXISTS', 'IF EXISTS'];

        STAR = /^\*/;

        SEPARATOR = /^,/;

        WHITESPACE = /^[ \n\r\t]+/;

        LITERAL = /^`?([a-z_][a-z0-9_]{0,})`?/i;

        NUMBER = /^[0-9]+(\.[0-9]+)?/;

        STRING = /^'([^']*)'/;

        DBLSTRING = /^"([^"]*)"/;

        PARAM_PLACEHOLDER = ['?'];

        return Lexer;

    })();

    exports.tokenize = function(sql, opts) {
        return (new Lexer(sql, opts)).tokens;
    };

    exports.tokenize2 = function(sql, opts) {
        var lexer;
        lexer = new Lexer(sql, opts);
        return {
            tokens: lexer.tokens,
            placeholders: lexer.params
        };
    };

}).call(this);